package pt.ist.meic.pava;

import java.util.ArrayList;

import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;


/**
 * @author Antonio Massingue
 * @author Joao Matos
 * @author Nuno Santos
 *
 *	This class represents a trait loaded by the user
 *
 */
public class LoadedTrait {

	/**
	 * The constructor class of the trait
	 */
	private CtClass traitCt;
	
	/**
	 * Is the trait aliased
	 */
	private boolean aliased;
	
	/**
	 * The aliases of this trait
	 */
	private String[] aliases;
	
	/**
	 * The list of the conflicts 
	 */
	private ArrayList<Conflict> conflicts;
	
	/**
	 * The methods required by this trait
	 */
	private ArrayList<CtMethod> requiredMethodsList;
	
	/**
	 * The methods implemented by this trait
	 */
	private ArrayList<TraitMethod> implementedMethodsList;

	
	
	
	
	
	
	/**
	 * The constructor
	 * 
	 * @param trait
	 */
	public LoadedTrait(CtClass trait) {

		this.traitCt = trait;
		this.implementedMethodsList = new ArrayList<TraitMethod>();
		this.requiredMethodsList = new ArrayList<CtMethod>();
		this.conflicts = new ArrayList<Conflict>();
		this.aliased = false;

	}

	
	
	/**
	 * Overloaded constructor
	 * 
	 * @param trait		The trait
	 * @param aliases	The aliases of this trait
	 */
	public LoadedTrait(CtClass trait, String[] aliases) {

		this.traitCt = trait;

		this.implementedMethodsList = new ArrayList<TraitMethod>();

		this.requiredMethodsList = new ArrayList<CtMethod>();

		this.conflicts = new ArrayList<Conflict>();

		this.aliased = true;

		this.aliases = aliases;

	}
	
	
	
	
	
	/**
	 * @return The name of this trait
	 */
	public String getName() {

		return this.traitCt.getName();
	}

	
	/**
	 * @return	The conflict referent to this trait
	 */
	public ArrayList<Conflict> getConflits() {

		return this.conflicts;
	}

	/**
	 * Add a conflict
	 */
	public void addConflit(TraitMethod a, TraitMethod b) {

		this.conflicts.add(new Conflict(a.getAlias(), a.getTraitClass(), b
				.getTraitClass()));

	}

	/**
	 * Get the index of a conflict in the list
	 * @param a		One of the traits involved
	 * @return		The index of the conflict in the list; -1 if not existent
	 */
	public int hasConflitedMethod(TraitMethod a) {

		int i = 0;

		for (Conflict conflit : this.conflicts) {
			if (a.getAlias().equals(conflit.getName()))
				return i;
			i++;
		}

		return -1;
	}

	/**
	 * Remove a conflict
	 * @param rem	The conflict to be removed
	 */
	public void removeConflit(Conflict rem) {
		this.conflicts.remove(rem);
	}

	/**
	 * Sets the list of required methods
	 * @param requiredMethods
	 */
	public void setRequiredMethods(ArrayList<CtMethod> requiredMethods) {
		this.requiredMethodsList = requiredMethods;
	}

	/**
	 * Sets the list of implemented methods
	 * @param implementedMethods
	 */
	public void setImplementedMethods(ArrayList<TraitMethod> implementedMethods) {
		this.implementedMethodsList = implementedMethods;
	}

	/**
	 * Adds a required method to the list
	 * @param requiredMethod
	 */
	public void addRequiredMethod(CtMethod requiredMethod) {
		this.requiredMethodsList.add(requiredMethod);
	}

	/**
	 * Adds an implemented method to the list
	 * @param implementedMethod
	 */
	public void addImplementeddMethod(TraitMethod implementedMethod) {
		this.implementedMethodsList.add(implementedMethod);
	}

	/**
	 * Removes a required method from the list
	 * @param requiredMethod
	 */
	public void removeRequiredMethod(CtMethod requiredMethod) {
		this.requiredMethodsList.remove(requiredMethod);
	}

	/**
	 * Removes an implemented method from the list
	 * @param implementedMethod
	 */
	public void removeImplementeddMethod(TraitMethod implementedMethod) {
		this.implementedMethodsList.remove(implementedMethod);
	}

	/**
	 * Gets a required method from the list given its index
	 * @param index	The index of the required method
	 * @return	The required method
	 */
	public CtMethod getRequiredMethod(int index) {
		return this.requiredMethodsList.get(index);

	}

	/**
	 * Gets an implemented method from the list given an alias
	 * @param alias		The alias
	 * @return		The implemented method
	 */
	public TraitMethod getImplementedMethod(String alias) {
		
		for(TraitMethod a : this.implementedMethodsList)
			if(a.getAlias().equals(alias))
				return a;
		
		return null;
	}
	
	
	/**
	 * Gets an implemented method from the list given its index
	 * @param index	The index of the required method
	 * @return	The implemented method
	 */	
	public TraitMethod getImplementeddMethod(int index) {
		return this.implementedMethodsList.get(index);
	}

	/**
	 * @return	The trait
	 */
	public CtClass getTrait() {
		return this.traitCt;
	}

	/**
	 * @return The list of required methods
	 */
	public ArrayList<CtMethod> getRequiredMethods() {
		return this.requiredMethodsList;
	}

	/**
	 * @return The list of implemented methods
	 */
	public ArrayList<TraitMethod> getImplementedMethods() {
		return this.implementedMethodsList;
	}

	/**
	 * @param requiredMethod	A method
	 * @return	true if this method exists in the requiredMethodsList; false otherwise
	 */
	public boolean containsRequiredMethod(CtMethod requiredMethod) {
		return this.requiredMethodsList.contains(requiredMethod);
	}
	
	
	
	/**
	 * Get the index of an implemented method in the list
	 * @param a		The implemented method
	 * @return		The index of the method in the list; -1 if not existent
	 */
	public int containsImplementedMethod(TraitMethod implementedMethod) {

		int i = 0;
		for (TraitMethod test : this.implementedMethodsList) {
			if (test.getAlias().equals(implementedMethod.getAlias()))
				return i;
			i++;
		}
		return -1;
	}

	/**
	 * Overloading
	 */
	public int containsImplementedMethod(CtMethod implementedMethod) {
		
		
		int i = 0;
		for (TraitMethod test : this.implementedMethodsList){
			if (test.getAlias().equals(implementedMethod.getName()))
				return i;
			i++;
		}
		return -1;

	}

	/**
	 * Overloading
	 */
	public boolean containsImplementedMethod(String implementedMethod) {

		for (TraitMethod test : this.implementedMethodsList)
			if (test.getAlias().equals(implementedMethod))
				return true;
		return false;

	}

	/**
	 * this method joins two lists of conflicts
	 */
	private void treatConflits(ArrayList<Conflict> list) {

		for (Conflict conflit : list)

			if (!this.containsImplementedMethod(conflit.getName()))

				this.conflicts.add(conflit);

	}


	/**
	 * this method joins two lists of loaded traits
	 */
	protected boolean joinTraits(ArrayList<LoadedTrait> list)
			throws NotFoundException {

		/** Join and treat conflits **/
		for (LoadedTrait tojoin2 : list)

			treatConflits(tojoin2.getConflits());

		for (LoadedTrait tojoin : list) {

			/** Join Implement Methods **/
			for (TraitMethod method : tojoin.getImplementedMethods()) {

				int conf = this.containsImplementedMethod(method);

				if (conf != -1) {

					/**
					 * Verify if the methods in conflit are from the same trait
					 * or If the method is from the composing Trait (precedence
					 * over subtraits). Otherwise are announced a conflit
					 */

					TraitMethod conflited = this.implementedMethodsList
							.get(conf);

					if (!conflited.getTraitClass().equals(
							method.getTraitClass())
							&& !conflited.getTraitClass().equals(
									this.traitCt.getName())) {

						this.addConflit(method, conflited);
						this.implementedMethodsList.remove(conflited);
					}
				} else {

					int veri = this.hasConflitedMethod(method);

					if (veri != -1)

						this.conflicts.get(veri)
								.addTrait(method.getTraitClass());

					else

						this.addImplementeddMethod(method);

					veri = meetRequirements(method);

					if (veri != -1)

						this.requiredMethodsList.remove(veri);
				}
			}

			/** Join interfaces **/
			joinInterfaces(tojoin.traitCt.getInterfaces());

		}

		/** Join required methods **/
		for (LoadedTrait tojoin3 : list)

			joinRequired(tojoin3.getRequiredMethods());

		return true;

	}

	/** 
	 * This method joins the super class content
	 * to this class
	 * 
	 * @param superCt	The superclass of this trait
	 * @throws NotFoundException
	 */
	protected void joinSuper(LoadedTrait superCt) throws NotFoundException {

		/** Join and treat conflits **/
		treatConflits(superCt.getConflits());

		/** join implemented methods **/
		for (TraitMethod method : superCt.getImplementedMethods()) {

			if (this.containsImplementedMethod(method) == -1) {
				this.addImplementeddMethod(method);

				/** Check if new method satisfies requirements **/
				int veri = meetRequirements(method);

				if (veri != -1)
					this.requiredMethodsList.remove(veri);

			}

		}

		/** join required methods **/
		joinRequired(superCt.getRequiredMethods());

		/** join interfaces **/
		joinInterfaces(superCt.getTrait().getInterfaces());

	}

	
	
	/**
	 * Get the index of a trait method in the list
	 * @param a		The trait method
	 * @return		The index of the method in the list; -1 if not existent
	 */
	private int meetRequirements(TraitMethod test) {

		String name = test.getAlias();

		int i = 0;
		for (CtMethod method : this.requiredMethodsList) {
			if (method.getName().equals(name))
				return i;
			i++;
		}

		return -1;

	}

	/** This method adds interfaces of subtraits **/
	private void joinInterfaces(CtClass[] interfaces) throws NotFoundException {

		for (CtClass interf : interfaces)
			if (!this.hasInterface(interf))
				this.traitCt.addInterface(interf);

	}

	/** This method adds requiredMethods of subtraits **/
	private void joinRequired(ArrayList<CtMethod> list) {

		for (CtMethod method2 : list)

			if (this.containsImplementedMethod(method2) == -1
					&& !this.containsRequiredMethod(method2))

				this.addRequiredMethod(method2);

	}

	/**
	 * Check whether an interface is present 
	 * @param interf	The interface
	 * @return	true if it is present; false otherwise
	 * @throws NotFoundException
	 */
	public boolean hasInterface(CtClass interf) throws NotFoundException {

		for (CtClass inter : this.traitCt.getInterfaces())
			if (inter.equals(interf))
				return true;

		return false;
	}

	/** Fill lists of the Trait **/
	protected void fillMethodsList() {

		for (CtMethod method : this.traitCt.getDeclaredMethods()) {

			if (Modifier.isAbstract(method.getModifiers()))
				/** If this method was not implemented **/

				this.addRequiredMethod(method);

			else {
				/** If was implemented **/
				
				if (this.aliased)

					
					this.addImplementeddMethod(new TraitMethod(method, this.findAlias(method.getName()), this.traitCt.getName()));

				else
					
					this.addImplementeddMethod(new TraitMethod(method,
							this.traitCt.getName()));
					
			}
		}
	}

	
	
	
	/**
	 * Is this trait aliased
	 * @return	true if so; false otherwise
	 */
	public boolean isAliaded() {
		return this.aliased;
	}

	
	
	/**
	 * Find an alias of some method
	 * @param method	The method given
	 * @return	The alias if exists; the method itself otherwise
	 */
	private String findAlias(String method) {

		for (int i = 0; i < aliases.length; i += 2)
			if (method.equals(this.aliases[i]))
				return this.aliases[++i];

		return method;
	}
	
	
	/** Update methods on implementedList **/
	protected void updateList(CtClass ct){
		
		this.traitCt = ct;
		
		int veri;
		
		for(CtMethod mt : ct.getDeclaredMethods()){
			
			
			veri = this.containsImplementedMethod(mt);
			
			TraitMethod toChange = this.implementedMethodsList.get(veri);
			
			this.implementedMethodsList.remove(veri);
			
			toChange.setMethod(mt);
			
			this.implementedMethodsList.add(toChange);
			
		}
		

	}
	
}
